﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Linq;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;

namespace WRJClient.UI.SystemMonitor
{
    public class HeadingGauge : UserControl
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="HeadingGauge"/> class.
        /// </summary>
        public HeadingGauge()
        {
            FromValue = 0;
            ToValue = 360;

            Canvas = new Canvas();
            Content = Canvas;

            StickRotateTransform = new RotateTransform(180);
            Stick = new Path
            {
                Data = Geometry.Parse("M972.8 738.133333 972.8 640 580.266667 418.133333 580.266667 213.333333c0 0 0-149.333333-68.266667-149.333333S443.733333 213.333333 443.733333 213.333333l0 204.8L51.2 640l0 98.133333 392.533333-85.333333 0 136.533333-136.533333 110.933333 0 59.733333 173.985185-32.616296c7.016296 9.386667 18.204444 15.54963 30.814815 15.54963s23.893333-6.068148 30.814815-15.54963l173.985185 32.616296 0-59.733333-136.533333-110.933333 0-136.533333L972.8 738.133333z"),
                Fill = Brushes.CornflowerBlue,
                Stretch = Stretch.Fill,
                RenderTransformOrigin = new Point(.5, .5),
                RenderTransform = StickRotateTransform
            };
            Canvas.Children.Add(Stick);
            Panel.SetZIndex(Stick, 1);

            Canvas.SetBinding(WidthProperty,
                new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this });
            Canvas.SetBinding(HeightProperty,
                new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this });

            SetCurrentValue(NeedleFillProperty, new SolidColorBrush(Color.FromRgb(69, 90, 100)));

            Stick.SetBinding(Shape.FillProperty,
                new Binding { Path = new PropertyPath(NeedleFillProperty), Source = this });

            SetCurrentValue(AnimationsSpeedProperty, TimeSpan.FromMilliseconds(500));
            SetCurrentValue(TicksForegroundProperty, new SolidColorBrush(Color.FromRgb(210, 210, 210)));
            SetCurrentValue(LabelsEffectProperty,
                new DropShadowEffect { ShadowDepth = 2, RenderingBias = RenderingBias.Performance });

            SizeChanged += (sender, args) =>
            {
                IsControlLaoded = true;
                Draw();
            };

        }

        #region Properties

        private Canvas Canvas { get; set; }
        private Path Stick { get; set; }
        private RotateTransform StickRotateTransform { get; set; }
        private bool IsControlLaoded { get; set; }

        /// <summary>
        /// The ticks step property
        /// </summary>
        public static readonly DependencyProperty TicksStepProperty = DependencyProperty.Register(
            "TicksStep", typeof(double), typeof(HeadingGauge),
            new PropertyMetadata(double.NaN, Redraw));
        /// <summary>
        /// Gets or sets the separation between every tick
        /// </summary>
        public double TicksStep
        {
            get { return (double)GetValue(TicksStepProperty); }
            set { SetValue(TicksStepProperty, value); }
        }

        /// <summary>
        /// The labels step property
        /// </summary>
        public static readonly DependencyProperty LabelsStepProperty = DependencyProperty.Register(
            "LabelsStep", typeof(double), typeof(HeadingGauge),
            new PropertyMetadata(double.NaN, Redraw));
        /// <summary>
        /// Gets or sets the separation between every label
        /// </summary>
        public double LabelsStep
        {
            get { return (double)GetValue(LabelsStepProperty); }
            set { SetValue(LabelsStepProperty, value); }
        }

        /// <summary>
        /// From value property
        /// </summary>
        public static readonly DependencyProperty FromValueProperty = DependencyProperty.Register(
            "FromValue", typeof(double), typeof(HeadingGauge),
            new PropertyMetadata(0d, Redraw));
        /// <summary>
        /// Gets or sets the minimum value of the gauge
        /// </summary>
        public double FromValue
        {
            get { return (double)GetValue(FromValueProperty); }
            set { SetValue(FromValueProperty, value); }
        }

        /// <summary>
        /// To value property
        /// </summary>
        public static readonly DependencyProperty ToValueProperty = DependencyProperty.Register(
            "ToValue", typeof(double), typeof(HeadingGauge),
            new PropertyMetadata(100d, Redraw));
        /// <summary>
        /// Gets or sets the maximum value of the gauge
        /// </summary>
        public double ToValue
        {
            get { return (double)GetValue(ToValueProperty); }
            set { SetValue(ToValueProperty, value); }
        }

        /// <summary>
        /// The value property
        /// </summary>
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
            "Value", typeof(double), typeof(HeadingGauge),
            new PropertyMetadata(default(double), ValueChangedCallback));
        /// <summary>
        /// Gets or sets the current gauge value
        /// </summary>
        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }


        /// <summary>
        /// The disablea animations property
        /// </summary>
        public static readonly DependencyProperty DisableaAnimationsProperty = DependencyProperty.Register(
            "DisableaAnimations", typeof(bool), typeof(HeadingGauge), new PropertyMetadata(default(bool)));
        /// <summary>
        /// Gets or sets whether the chart is animated
        /// </summary>
        public bool DisableaAnimations
        {
            get { return (bool)GetValue(DisableaAnimationsProperty); }
            set { SetValue(DisableaAnimationsProperty, value); }
        }

        /// <summary>
        /// The animations speed property
        /// </summary>
        public static readonly DependencyProperty AnimationsSpeedProperty = DependencyProperty.Register(
            "AnimationsSpeed", typeof(TimeSpan), typeof(HeadingGauge), new PropertyMetadata(default(TimeSpan)));
        /// <summary>
        /// Gets or sets the animations speed
        /// </summary>
        public TimeSpan AnimationsSpeed
        {
            get { return (TimeSpan)GetValue(AnimationsSpeedProperty); }
            set { SetValue(AnimationsSpeedProperty, value); }
        }

        /// <summary>
        /// The ticks foreground property
        /// </summary>
        public static readonly DependencyProperty TicksForegroundProperty = DependencyProperty.Register(
            "TicksForeground", typeof(Brush), typeof(HeadingGauge), new PropertyMetadata(default(Brush)));
        /// <summary>
        /// Gets or sets the ticks foreground
        /// </summary>
        public Brush TicksForeground
        {
            get { return (Brush)GetValue(TicksForegroundProperty); }
            set { SetValue(TicksForegroundProperty, value); }
        }

        /// <summary>
        /// The needle fill property
        /// </summary>
        public static readonly DependencyProperty NeedleFillProperty = DependencyProperty.Register(
            "NeedleFill", typeof(Brush), typeof(HeadingGauge), new PropertyMetadata(default(Brush)));
        /// <summary>
        /// Gets o sets the needle fill
        /// </summary>
        public Brush NeedleFill
        {
            get { return (Brush)GetValue(NeedleFillProperty); }
            set { SetValue(NeedleFillProperty, value); }
        }

        /// <summary>
        /// The labels effect property
        /// </summary>
        public static readonly DependencyProperty LabelsEffectProperty = DependencyProperty.Register(
            "LabelsEffect", typeof(Effect), typeof(HeadingGauge), new PropertyMetadata(default(Effect)));

        /// <summary>
        /// Gets or sets the labels effect.
        /// </summary>
        /// <value>
        /// The labels effect.
        /// </value>
        public Effect LabelsEffect
        {
            get { return (Effect)GetValue(LabelsEffectProperty); }
            set { SetValue(LabelsEffectProperty, value); }
        }

        /// <summary>
        /// The ticks stroke thickness property
        /// </summary>
        public static readonly DependencyProperty TicksStrokeThicknessProperty = DependencyProperty.Register(
            "TicksStrokeThickness", typeof(double), typeof(HeadingGauge), new PropertyMetadata(2d));

        /// <summary>
        /// Gets or sets the ticks stroke thickness.
        /// </summary>
        /// <value>
        /// The ticks stroke thickness.
        /// </value>
        public double TicksStrokeThickness
        {
            get { return (double)GetValue(TicksStrokeThicknessProperty); }
            set { SetValue(TicksStrokeThicknessProperty, value); }
        }

        #endregion

        private static void ValueChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var ag = (HeadingGauge)o;
            ag.MoveStick();
        }

        private static void Redraw(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var ag = (HeadingGauge)o;
            ag.Draw();
        }

        private void MoveStick()
        {
            int wedge = 360;

            var fromAlpha = (360 - wedge) * .5;
            var toAlpha = 360 - fromAlpha;

            var alpha = LinearInterpolation(fromAlpha, toAlpha, FromValue, ToValue, Value);

            if (DisableaAnimations)
            {
                StickRotateTransform.Angle = alpha;
            }
            else
            {
                StickRotateTransform.BeginAnimation(RotateTransform.AngleProperty,
                    new DoubleAnimation(alpha, AnimationsSpeed));
            }
        }

        internal void Draw()
        {
            if (!IsControlLaoded) return;

            //No cache for you gauge :( kill and redraw please
            foreach (var child in Canvas.Children.Cast<UIElement>()
                .Where(x => !Equals(x, Stick)).ToArray())
                Canvas.Children.Remove(child);

            int wedge = 360;

            var fromAlpha = (360 - wedge) * .5;
            var toAlpha = 360 - fromAlpha;

            var d = ActualWidth < ActualHeight ? ActualWidth : ActualHeight;

            Stick.Height = d * .5 * .8;
            Stick.Width = Stick.Height;

            Canvas.SetLeft(Stick, ActualWidth * .5 - Stick.Width * .5);
            Canvas.SetTop(Stick, ActualHeight * .5 - Stick.Height * .5);

            var ticksHi = d * .5;
            var ticksHj = d * .47;
            var labelsHj = d * .44;

            var ts = 10;
            for (var i = FromValue; i <= ToValue; i += ts)
            {
                var alpha = LinearInterpolation(fromAlpha, toAlpha, FromValue, ToValue, i) - 90;

                var tick = new Line
                {
                    X1 = ActualWidth * .5 + ticksHi * Math.Cos(alpha * Math.PI / 180),
                    X2 = ActualWidth * .5 + ticksHj * Math.Cos(alpha * Math.PI / 180),
                    Y1 = ActualHeight * .5 + ticksHi * Math.Sin(alpha * Math.PI / 180),
                    Y2 = ActualHeight * .5 + ticksHj * Math.Sin(alpha * Math.PI / 180)
                };
                Canvas.Children.Add(tick);
                tick.SetBinding(Shape.StrokeProperty,
                    new Binding { Path = new PropertyPath(TicksForegroundProperty), Source = this });
                tick.SetBinding(Shape.StrokeThicknessProperty,
                    new Binding { Path = new PropertyPath(TicksStrokeThicknessProperty), Source = this });
            }

            var ls = 30;
            for (var i = 0; i < 360; i += ls)
            {
                var alpha = LinearInterpolation(fromAlpha, toAlpha, FromValue, ToValue, i) - 90;

                var tick = new Line
                {
                    X1 = ActualWidth * .5 + ticksHi * Math.Cos(alpha * Math.PI / 180),
                    X2 = ActualWidth * .5 + labelsHj * Math.Cos(alpha * Math.PI / 180),
                    Y1 = ActualHeight * .5 + ticksHi * Math.Sin(alpha * Math.PI / 180),
                    Y2 = ActualHeight * .5 + labelsHj * Math.Sin(alpha * Math.PI / 180)
                };
                Canvas.Children.Add(tick);
                string l;
                switch (i)
                {
                    case 0:
                        l = "N";
                        break;
                    case 90:
                        l = "E";
                        break;
                    case 180:
                        l = "S";
                        break;
                    case 270:
                        l = "W";
                        break;
                    default:
                        l = i.ToString();
                        break;
                }
                var label = new TextBlock
                {
                    Text = l
                };
                label.SetBinding(EffectProperty,
                    new Binding { Path = new PropertyPath(LabelsEffectProperty), Source = this });
                Canvas.Children.Add(label);
                label.UpdateLayout();

                Canvas.SetLeft(label, alpha > 90 ? tick.X2 : (Math.Abs(alpha - 90) < 4 ? tick.X2 - label.ActualWidth * .5 : tick.X2 - label.ActualWidth));
                Canvas.SetTop(label, alpha < 0 || alpha > 180 ? tick.Y2 : (Math.Abs(alpha) < 4 || Math.Abs(alpha - 180) < 4 ? tick.Y2 - label.ActualHeight * .5 : tick.Y2 - label.ActualHeight));

                tick.SetBinding(Shape.StrokeProperty,
                    new Binding { Path = new PropertyPath(TicksForegroundProperty), Source = this });
                tick.SetBinding(Shape.StrokeThicknessProperty,
                    new Binding { Path = new PropertyPath(TicksStrokeThicknessProperty), Source = this });
            }
            MoveStick();
        }

        private static double LinearInterpolation(double fromA, double toA, double fromB, double toB, double value)
        {
            var p1 = new Point(fromB, fromA);
            var p2 = new Point(toB, toA);

            var deltaX = p2.X - p1.X;
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            var m = (p2.Y - p1.Y) / (deltaX == 0 ? double.MinValue : deltaX);

            return m * (value - p1.X) + p1.Y;
        }

        private static double DecideInterval(double minimum)
        {
            var magnitude = Math.Pow(10, Math.Floor(Math.Log(minimum) / Math.Log(10)));

            var residual = minimum / magnitude;
            double tick;
            if (residual > 5)
                tick = 10 * magnitude;
            else if (residual > 2)
                tick = 5 * magnitude;
            else if (residual > 1)
                tick = 2 * magnitude;
            else
                tick = magnitude;

            return tick;
        }
    }
}